import { ThrottleUtilParamFun, ThrottleUtilReturn } from '../types'

/**
 * 超出函数调用次数时的处理策略
 */
export const LimitCallExceedStrategyConst = {
  IGNORE: 'ignore',
  THROW: 'throw',
  CALLBACK: 'callback',
  REPLACE: 'replace'
} as const

export type LimitCallExceedStrategyConstType =
  (typeof LimitCallExceedStrategyConst)[keyof typeof LimitCallExceedStrategyConst]

export type LimitCallOptionsType = {
  maxCount?: number
  exclude?: () => boolean
  thisArg?: any
  exceedStrategy?: LimitCallExceedStrategyConstType
  handler?: (...args: any[]) => Promise<any> | any
}

/**
 * 限制函数的调用次数，当函数的调用次数达到上限时，根据配置的超出函数调用次数处理策略，进行相
 * 应的处理
 *
 * @param {ThrottleUtilParamFun} fun 要进行限制调用次数的函数
 * @param {LimitCallOptionsType} options 配置选项
 * - maxCount: 允许的最大调用次数，默认为 1，如果设置为 -1，则表示不限制调用次数
 * - exclude: 不执行函数的条件，返回 true 时不执行函数，即不消耗函数的调用次数，默认为 `() => false`，
 * 即不存在不执行函数的条件
 * - thisArg 指定需要限制调用次数的函数(传统函数)调用时的 this 指向，默认为 null，即非严
 * 格模式下函数调用时的 this 指向全局对象，严格模式下为 thisArg 指定的值；对于需要节流处理
 * 的函数为箭头函数时，该参数无效，箭头函数的 this 指向由函数定义时确定，不受 thisArg 参数
 * 影响，箭头函数的 this 来源于箭头函数定义时的上级作用域。
 * - exceedStrategy: 超出函数调用次数时的处理策略，默认为 {@link LimitCallExceedStrategyConst.IGNORE}，
 * 可选值：
 *   - {@link LimitCallExceedStrategyConst.IGNORE}: 不进行任何处理（默认）
 *   - {@link LimitCallExceedStrategyConst.THROW}: 抛出错误 `函数的调用次数已达到上限（${maxCount}）`
 *   - {@link LimitCallExceedStrategyConst.CALLBACK}: 执行由 options.handler 配置选项指定的回调函数
 *   - {@link LimitCallExceedStrategyConst.REPLACE}: 执行由 options.handler 配置选项指定的代替函数
 * - handler: 当 options.exceedStrategy 配置选项为 {@link LimitCallExceedStrategyConst.CALLBACK} 或
 * {@link LimitCallExceedStrategyConst.REPLACE} 时，回调用执行由该参数指定的回调函数或代替函数，该函数接收调用函数的
 * 参数和一个 {@link reset} 方法，调用 {@link reset} 方法可以重置调用次数计数器，使得原本的函数可以再次调用
 * @returns {ThrottleUtilReturn} 限制调用次数后的函数
 */
export const limitCall = (
  fun: ThrottleUtilParamFun,
  options?: LimitCallOptionsType
): ThrottleUtilReturn => {
  const { maxCount, exclude, thisArg, exceedStrategy, handler } = {
    maxCount: 1,
    exclude: () => false,
    thisArg: null,
    exceedStrategy: LimitCallExceedStrategyConst.IGNORE,
    handler: async () => {},
    ...options
  }
  // 记录当前调用次数
  let count = 0
  // 重置函数已被调用的次数
  const reset = () => (count = 0)
  return async (...args: any[]): Promise<void | any> => {
    // 如果还可以调用函数
    if (count < maxCount || maxCount === -1) {
      // 如果不需要执行函数
      if (exclude()) return
      count++
      return await fun.apply(thisArg, args)
    }
    // 调用次数达到上限
    if (exceedStrategy === LimitCallExceedStrategyConst.THROW) {
      throw new Error(`函数的调用次数已达到上限（${maxCount}）`)
    } else if (exceedStrategy === LimitCallExceedStrategyConst.CALLBACK) {
      return await handler.apply(thisArg, [...args, reset])
    } else if (exceedStrategy === LimitCallExceedStrategyConst.REPLACE) {
      return await handler.apply(thisArg, [...args, reset])
    }
  }
}

export type LimitCallType = typeof limitCall
